package kr.ac.ssu.ailab.Control;

import java.awt.Point;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Vector;
import kr.ac.ssu.ailab.Model.Data.Cell;
import kr.ac.ssu.ailab.Model.Data.Agent.MSG;
import kr.ac.ssu.ailab.View.SeqTest;

/**
 * Thread 로 구현된 Heuristic PathFininding 을 하는 Class
 * 
 * @author Neil
 * 
 */
public class NewHeuristicPathFinding extends Thread
{
	private static int tempCount = 3;
	public final int TEST_TIME = 10;

	public final int NO_PATH = -1;
	public final int NOT_FOUND = 0;
	public final int FOUND = 1;
	public final int SHOW = 3;

	private Thread loop;
	private Vector<Cell> openList = null;
	private Vector<Cell> closedList = null;

	private Cell finish = null;
	private Cell first = null;

	private static Vector<Cell> goalList;
	private static Vector<Cell> pathList;
	private boolean match = false;
	private boolean finalState = false;

	private static Runtime runtime = Runtime.getRuntime();

	public static int stepSpeed = 20;
	private static NewHeuristicPathFinding newHeuristicPathFinding = null;

	private Cell nowCell = null;
	public static boolean change;

	/**
	 * 생성자
	 */
	@SuppressWarnings("unchecked")
	public NewHeuristicPathFinding()
	{
		loop = new Thread(this);
		openList = new Vector<Cell>();
		closedList = new Vector<Cell>();
		goalList = GraphControl.getInstance().getGoalList();
		pathList = new Vector<Cell>();
	}

	/**
	 * New HeuristicPathFinding의 Instance를 반환하는 method
	 * 
	 * @return
	 */
	public static NewHeuristicPathFinding getInstance()
	{
		if (newHeuristicPathFinding == null)
		{
			newHeuristicPathFinding = new NewHeuristicPathFinding();
		}
		return newHeuristicPathFinding;
	}

	/**
	 * New HeuristicPathFinding Instance 를 null 로 초기화하는 메소드
	 */
	public static void initInstance()
	{
		if (newHeuristicPathFinding != null)
			newHeuristicPathFinding = null;
	}

	/**
	 * Heuristic 에서 복사한 GoalList를 Set 하는 메소드
	 * 
	 * @param goalList
	 */
	public static void setGoalList(Vector<Cell> goalList)
	{
		NewHeuristicPathFinding.goalList = goalList;
	}

	/**
	 *부분 midpath 로 구성되어진 GoalList를 get 하는 method
	 * 
	 * @return
	 */
	public static Vector<Cell> getGoalList()
	{
		return goalList;
	}

	/**
	 * 경로를 찾은 PathList를 get 하는 method
	 * 
	 * @return
	 */
	public static Vector<Cell> getPathList()
	{
		return pathList;
	}

	/**
	 * 탐색을 시작 하는 메소드
	 */
	public void startSearch()
	{
		openList = new Vector<Cell>();
		closedList = new Vector<Cell>();
		this.loop.start();
	}

	/**
	 * 해당 쓰레드의 Loop 를 get 하는 메소드
	 * 
	 * @return
	 */
	public Thread getLoop()
	{
		return loop;
	}

	/**
	 * 현재 위치를 반환 하는 메소드
	 * 
	 * @return
	 */
	public Cell getFirst()
	{
		return first;
	}

	/**
	 * 목표점을 반환하는 메소드
	 * 
	 * @param finish
	 */
	public Cell getFinish()
	{
		return this.finish;
	}

	/**
	 * 목표점을 set 하는 메소드
	 * 
	 * @param finish
	 */
	public void setFinish(Cell finish)
	{
		this.finish = finish;
	}

	@SuppressWarnings("static-access")
	@Override
	public void run()
	{
		int state = NOT_FOUND;

		while (!finalState)
		{
			// 여기서 Dynamic Test 하면 되긴함................
			if (goalList.size() > 0)
			{
				if (finish == null)
				{
					first = SeqTest.mapControl.getStartCell();
				}
				else
				{
					first = finish;
				}
				finish = goalList.lastElement();
			}
			else if (goalList.isEmpty())
			{

				first = finish;
				finish = SeqTest.mapControl.getFinishCell();
			}
			else
			{
				first = SeqTest.mapControl.getStartCell();
				finish = SeqTest.mapControl.getFinishCell();
			}
			ClusterControl instanceCluster = ClusterControl.getInstance();
			int NewClusterControlNumX = SeqTest.mapControl.getMapNumX() / instanceCluster.getNumX();
			int NewClusterControlNumY = SeqTest.mapControl.getMapNumY() / instanceCluster.getNumY();

			if (first.getPosition().x / NewClusterControlNumX == finish.getPosition().x
					/ NewClusterControlNumX)
			{
				if (first.getPosition().y / NewClusterControlNumY == finish.getPosition().y
						/ NewClusterControlNumY)
				{
					match = true;
				}
				else
				{
					match = false;
				}

			}
			else
			{
				match = false;
			}
			while (state == NOT_FOUND)
			{
				try
				{
					switch (SeqTest.mapControl.getLevel())
						{
						case 1:
							state = step_Level1();
							loop.sleep(stepSpeed);
							break;
						case 2:
							state = step_Level2();
							loop.sleep(stepSpeed);
							break;
						case 3:
							if (!change)
							{
								state = step_Level2();
								loop.sleep(stepSpeed);
							}
							else
								return;
						case 4:
							if (!change)
							{
								state = step_Level2();
								loop.sleep(stepSpeed);
							}
							else
								return;
						case 5:
							if (!change)
							{
								state = step_Level2();
								loop.sleep(stepSpeed);
							}
							else
								return;
						default:
							System.err.println("잘못된 난이도 입니다.");

						}

				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}

			/* Show optimized path if being found */
			if (state == FOUND)
			{
				state = NOT_FOUND;
				setPath();

				if (goalList.size() > 0)
					goalList.remove(goalList.size() - 1);
				for (int i = 0; i < openList.size(); i++)
				{
					openList.get(i).setParentPoint(null);
				}
				for (int i = 0; i < closedList.size(); i++)
				{
					closedList.get(i).setParentPoint(null);
				}

				openList = new Vector<Cell>();
				closedList = new Vector<Cell>();

				SeqTest.agentGroup.setGroupPath(pathList);
				SeqTest.agentGroup.moveGroup();
				tempCount--;
				if (tempCount == 0)
				{
					SeqTest.agentGroup.getAgentByID("agent1").setDead();
					SeqTest.agentGroup.sendMsg(new MSG(MSG.BEING_ATTACKED, 50), "agent0");
				}

			}
			else
			{
				// System.out.println("No Path Found");
				loop.interrupt();
			}
		}

	}

	/**
	 * Lv1 수준의 이동 Method method
	 * 
	 * @return
	 */
	public int step_Level1()
	{

		boolean found = false;

		openList.addElement(first);

		Vector<Cell> temp = (Vector<Cell>) openList.clone();

		double min = Double.MAX_VALUE;
		double score;

		Cell best = (Cell) temp.elementAt(temp.size() - 1);

		for (int i = 0; i < temp.size(); i++)
		{
			nowCell = (Cell) temp.elementAt(i);
			if (!closedList.contains(nowCell))
			{
				score = nowCell.getDistFromStart();
				score += 1.0;
				score += nowCell.getDistFromFinish();

				if (score < min)
				{
					min = score;
					best = nowCell;
				}
			}
		}
		nowCell = best;
		openList.removeElement(nowCell);
		closedList.addElement(nowCell);

		if (nowCell.getType() == CellControl.NORMAL || nowCell.getType() == CellControl.OPENLIST
				|| nowCell.getType() == CellControl.HARD)
			nowCell.setType(CellControl.CLOSEDLIST);

		Cell next[] = SeqTest.mapControl.getAdjacentCell(nowCell);
		int length = next.length;
		for (int i = 0; i < length; i++)
		{
			if (next[i] != null)
			{
				if (next[i] == finish)
				{
					found = true;
				}
				// if (next[i].getType() != CellControl.OBSTACLE)
				if (!next[i].isObstacle())
				{
					next[i].setParentPoint(nowCell.getPosition());
					next[i].setDistFromStart(nowCell.getDistFromStart() + 1);
					next[i].setDistFromFinish(tie_breaking_Heuristic(next[i], finish, 1));
					if (!openList.contains(next[i]) && !closedList.contains(next[i]))
					{
						openList.addElement(next[i]);
						if (next[i].getType() != CellControl.STAND_CELL)
							next[i].setType(CellControl.OPENLIST);

					}
				}
			}

			if (found)
			{
				System.err.println("Found goal at " + finish.getPosition());
				return FOUND;
			}
		}

		if (openList.size() == 0)
		{
			return NO_PATH;
		}

		return NOT_FOUND;
	}

	/**
	 * Lv2 수준의 이동 Method
	 * 
	 * @return
	 */
	public int step_Level2()
	{

		boolean found = false;

		openList.addElement(first);

		Vector<Cell> temp = (Vector<Cell>) openList.clone();

		double min = Double.MAX_VALUE;
		double score;

		Cell best = (Cell) temp.elementAt(temp.size() - 1);

		for (int i = 0; i < temp.size(); i++)
		{
			nowCell = (Cell) temp.elementAt(i);
			if (!closedList.contains(nowCell))
			{
				score = nowCell.getDistFromStart();
				score += nowCell.getWeight();
				score += nowCell.getDistFromFinish();

				if (score < min)
				{
					min = score;
					best = nowCell;
				}
			}
		}
		nowCell = best;
		openList.removeElement(nowCell);
		closedList.addElement(nowCell);

		if (nowCell.getType() == CellControl.NORMAL || nowCell.getType() == CellControl.OPENLIST
				|| nowCell.getType() == CellControl.HARD)
			nowCell.setType(CellControl.CLOSEDLIST);

		Cell next[] = SeqTest.mapControl.getAdjacentCell(nowCell);
		int length = next.length;
		for (int i = 0; i < length; i++)
		{
			if (next[i] != null)
			{
				if (next[i] == finish)
				{
					found = true;
				}
				if (!next[i].isObstacle())
				{
					next[i].setParentPoint(nowCell.getPosition());
					next[i].setDistFromStart(nowCell.getDistFromStart() + 1);
					next[i].setDistFromFinish(tie_breaking_Heuristic(next[i], finish, 1));
					if (!openList.contains(next[i]) && !closedList.contains(next[i]))
					{
						openList.addElement(next[i]);
						if (next[i].getType() != CellControl.STAND_CELL)
							next[i].setType(CellControl.OPENLIST);

					}
				}
			}

			if (found)
			{
				System.err.println("Found goal at " + finish.getPosition());
				return FOUND;
			}
		}

		if (openList.size() == 0)
		{
			return NO_PATH;
		}

		return NOT_FOUND;
	}

	/**
	 * 길을 탐색하여 길이 있으면 이동 경로를 결정해주는 Method
	 */
	public void setPath()
	{
		boolean finished = false;
		Cell next;
		Cell stop = first;
		Cell now = finish;
		pathList = new Vector<Cell>();
		try
		{
			while (!finished)
			{
				if (now == SeqTest.mapControl.getFinishCell())
				{

					finalState = true;
					System.out.println("길을 찾았습니다.!!!");
				}
				next = SeqTest.mapControl.getCellArray()[now.getParentPoint().x][now
						.getParentPoint().y];

				now = next;
				if (now.getType() != CellControl.STAND_CELL)
				{
					CellControl.setCellType(now, CellControl.PART_OF_PATH);
					pathList.addElement(now);
				}
				if (now == stop)
				{
					System.out.println("Path Found");
					finished = true;
				}
			}

		}

		catch (NullPointerException e)
		{

		}

	}

	/**
	 * AStar 를 이용할때 사용하는 Heuristic (diagonal 방식)
	 * 
	 * @param nodeA
	 * @param nodeB
	 * @param low
	 * @return
	 */
	public static double calDist(Cell nodeA, Cell nodeB, double low)
	{
		int nodeAX = nodeA.getPosition().x;
		int nodeAY = nodeA.getPosition().y;
		int nodeBX = nodeB.getPosition().x;
		int nodeBY = nodeB.getPosition().y;

		double h_diagonal = Math.min(Math.abs(nodeAX - nodeBX), Math.abs(nodeAY - nodeBY));
		double h_straight = ((Math.abs(nodeAX - nodeBX) + Math.abs(nodeAY - nodeBY)));
		double result = (low * Math.sqrt(2)) * h_diagonal + low * (h_straight - 2 * h_diagonal);
		// System.out.println("Cost : " + result);
		return result;
	}

	/**
	 * AStar 를 이용할때 사용하는 Heuristic (Tie-breaking 방식)
	 * 
	 * @param nodeA
	 * @param nodeB
	 * @param low
	 * @return
	 */
	public static double tie_breaking_Heuristic(Cell nodeA, Cell nodeB, double low)
	{
		int nodeAX = nodeA.getPosition().x;
		int nodeAY = nodeA.getPosition().y;
		int nodeBX = nodeB.getPosition().x;
		int nodeBY = nodeB.getPosition().y;
		Point start = SeqTest.mapControl.getStartCell().getPosition();

		double dx1 = nodeAX - nodeBX;
		double dy1 = nodeAY - nodeBY;
		double dx2 = start.x - nodeBX;
		double dy2 = start.y - nodeBY;
		double cross = dx1 * dy2 - dx2 * dy1;
		if (cross < 0)
			cross = -cross; // absolute value

		/**/
		return (low * (Math.abs(dx1) + Math.abs(dy1) + cross * 0.0002)) * 2;

	}

	/**
	 * AStar 를 이용할때 사용하는 Heuristic (manhattan 방식)
	 * 
	 * @param nodeA
	 * @param nodeB
	 * @param low
	 * @return
	 */
	public static double manhattan(Cell nodeA, Cell nodeB, double low) // Manhattan
	// Distance
	{
		int nodeAX = nodeA.getPosition().x;
		int nodeAY = nodeA.getPosition().y;
		int nodeBX = nodeB.getPosition().x;
		int nodeBY = nodeB.getPosition().y;
		// (abs(n.x-goal.x) + abs(n.y-goal.y))

		double h_straight = ((Math.abs(nodeAX - nodeBX) + Math.abs(nodeAY - nodeBY)));
		double result = +low * h_straight;
		return result;
		// return low * (Math.abs(a.x - b.x) + Math.abs(a.y - b.y) - 1);
	}

}
